Popular Searches
Popular Course Categories
Popular Courses

Creating Threads by Extending the Thread Class

Creating Threads by Extending the Thread Class

Multithreading and Concurrency Fundamentals

Creating Threads by Extending the Thread Class in Java

Course: Core Java Training

Topic: Creating Threads by Extending the Thread Class

Course Link: Core Java Training Course

Demo Link: Register for a Course Demo

JustAcademy Website: Visit JustAcademy


1. Introduction

Multithreading is an important feature of Java that allows a program to perform multiple tasks concurrently. A thread is an independent path of execution within a process. Java provides the built-in Thread class to create, start, and manage threads.

One basic way to create a thread is to define a class that extends Thread and overrides its run() method. The run() method contains the statements that should execute when the thread starts.

For example, an application might use a separate thread to process a report while the main thread continues handling other work. Extending Thread provides a straightforward way to demonstrate this behavior.

2. Learning Objectives

  • Understand the purpose of the Java Thread class.
  • Learn how to create a custom thread by extending Thread.
  • Override the run() method to define a task.
  • Understand how and why to call start().
  • Distinguish between calling start() and calling run() directly.
  • Learn how multiple thread objects execute concurrently.
  • Understand common mistakes and best practices for thread creation.

3. What Is the Thread Class?

The Thread class belongs to the java.lang package, which is automatically available in a Java program. It represents a thread of execution and provides methods for starting a thread, checking its state, interrupting it, waiting for it to finish, and retrieving information about it.

Important methods include:

Method Purpose
start() Starts a new thread of execution.
run() Defines the task executed by the thread.
sleep(long millis) Pauses the currently executing thread for approximately the specified duration, subject to scheduling.
join() Waits for another thread to terminate.
currentThread() Returns the currently executing thread.
getName() Returns the thread's name.
setName(String name) Sets the thread's name.
isAlive() Checks whether a thread has started and has not yet terminated.
interrupt() Requests interruption of a thread.
getState() Returns the thread's current Java state.

4. Syntax for Creating a Thread by Extending Thread

The general syntax is:

class MyThread extends Thread {
    @Override
    public void run() {
        // Task to execute in this thread
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

Explanation:

  • class MyThread extends Thread creates a subclass of the Thread class.
  • run() is overridden to define the task.
  • new MyThread() creates a thread object in the NEW state.
  • start() asks the JVM to start the new thread of execution.
  • The new thread invokes its run() method while the main thread can continue its own work.

5. Step-by-Step Example

Let's create a simple thread that displays a message.

class WelcomeThread extends Thread {
    @Override
    public void run() {
        System.out.println("Welcome from the child thread!");
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        WelcomeThread thread = new WelcomeThread();
        thread.start();

        System.out.println("Main thread is executing.");
    }
}

Possible output:

Main thread is executing.
Welcome from the child thread!

The output order is not guaranteed. Depending on scheduling, the child thread may print its message before the main thread prints its message.

How the program works

  1. The JVM starts the application and executes the main() method on the main thread.
  2. The statement new WelcomeThread() creates a thread object.
  3. The call to start() initiates a new thread of execution.
  4. The new thread executes the overridden run() method.
  5. The main thread continues executing independently of the child thread.

6. Understanding the run() Method

The run() method contains the code that the thread should execute. When extending Thread, override this method to define the thread's behavior.

class MessageThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread task started.");
        System.out.println("Thread task completed.");
    }
}

Overriding run() alone does not start a separate thread. The thread object must be started using start() to initiate a new execution path.

7. Difference Between start() and run()

This is one of the most important concepts when creating threads in Java.

Feature start() run()
Purpose Starts a new thread of execution. Contains the task's execution logic.
Execution The JVM invokes run() on the started thread. A direct call runs like an ordinary method call on the current thread.
Concurrency Allows the new thread to execute concurrently with other threads. Does not create a new thread by itself.
Repeated calls Calling it again on the same thread object throws IllegalThreadStateException. Calling it directly again simply invokes the method again on the calling thread.

Example: Calling run() directly

class DemoThread extends Thread {
    @Override
    public void run() {
        System.out.println(
            "Executing on: " + Thread.currentThread().getName()
        );
    }
}

public class RunMethodExample {
    public static void main(String[] args) {
        DemoThread thread = new DemoThread();

        thread.run();
        System.out.println(
            "Main executes on: " + Thread.currentThread().getName()
        );
    }
}

Output:

Executing on: main
Main executes on: main

Since run() is called directly, both statements execute on the main thread. To start a separate thread, use thread.start().

```html

8. Creating a Thread with a Constructor

The Thread class provides constructors that allow you to assign a name to a thread. Naming threads makes it easier to identify them in logs, debugging tools, and monitoring output.

class NamedThread extends Thread {
    public NamedThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println("Thread name: " +
            Thread.currentThread().getName());
    }
}

public class NamedThreadExample {
    public static void main(String[] args) {
        NamedThread thread = new NamedThread("Worker-1");
        thread.start();
    }
}

Output:

Thread name: Worker-1

Explanation:

  • The constructor receives a thread name.
  • super(name) passes that name to the parent Thread constructor.
  • start() starts the thread.
  • Thread.currentThread().getName() retrieves the name of the thread currently executing the code.

9. Creating Multiple Threads by Extending Thread

A Java program can create multiple objects of a class that extends Thread. Each object represents a separate thread and can execute the overridden run() method independently.

class NumberThread extends Thread {
    private final String taskName;

    public NumberThread(String taskName) {
        super(taskName);
        this.taskName = taskName;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(taskName + ": " + i);
        }
    }
}

public class MultipleThreadDemo {
    public static void main(String[] args) {
        NumberThread thread1 = new NumberThread("Thread A");
        NumberThread thread2 = new NumberThread("Thread B");

        thread1.start();
        thread2.start();
    }
}

Possible output:

Thread A: 1
Thread B: 1
Thread A: 2
Thread B: 2
Thread B: 3
Thread A: 3
Thread A: 4
Thread B: 4
Thread A: 5
Thread B: 5

The output order can differ on each execution. Each thread has its own execution path, and the scheduler determines how their work is interleaved.

10. Using sleep() in a Thread

The static method Thread.sleep() pauses the currently executing thread for a specified duration. It is commonly used in demonstrations, simulations, and tasks that need timed delays.

class CountingThread extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("Count: " + i);

            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                System.out.println("Thread interrupted.");
                return;
            }
        }
    }
}

public class SleepExample {
    public static void main(String[] args) {
        CountingThread thread = new CountingThread();
        thread.start();
    }
}

Expected behavior: The thread prints numbers with an approximately half-second pause between iterations. Actual timing depends on the operating system and JVM scheduler.

Important points:

  • sleep() pauses the current thread, not every thread in the application.
  • The method can throw InterruptedException.
  • Restoring the interrupt status with Thread.currentThread().interrupt() preserves the interruption signal when the method handles the exception.
  • Sleeping does not guarantee that the thread will resume at the exact end of the requested duration.

11. Waiting for a Thread with join()

The join() method allows one thread to wait until another thread finishes. It is useful when the main thread needs to wait for the result of a worker thread before continuing.

class TaskThread extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 3; i++) {
            System.out.println("Task: " + i);
        }
    }
}

public class JoinExample {
    public static void main(String[] args) {
        TaskThread thread = new TaskThread();
        thread.start();

        try {
            thread.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.out.println("Main thread interrupted.");
            return;
        }

        System.out.println("Task completed. Main continues.");
    }
}

Output:

Task: 1
Task: 2
Task: 3
Task completed. Main continues.

The main thread waits at join() until the worker thread terminates. This ensures the completion message appears after the worker's output.

12. Checking Thread State and Activity

Java provides methods for checking whether a thread is alive and retrieving its current state. These methods are useful for understanding thread execution, although a state check is only a snapshot and can change immediately afterward.

class StateThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread is executing.");
    }
}

public class ThreadStateExample {
    public static void main(String[] args) throws InterruptedException {
        StateThread thread = new StateThread();

        System.out.println(thread.getState());

        thread.start();
        thread.join();

        System.out.println(thread.getState());
        System.out.println(thread.isAlive());
    }
}

Output:

NEW
Thread is executing.
TERMINATED
false

The initial state is NEW. After the thread completes and join() returns, its state is TERMINATED and isAlive() returns false.

13. Thread Priority

Java allows a thread to have a priority value. The standard priority range is from Thread.MIN_PRIORITY (1) to Thread.MAX_PRIORITY (10), with Thread.NORM_PRIORITY (5) as the default.

class PriorityThread extends Thread {
    public PriorityThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(
            getName() + " priority: " + getPriority()
        );
    }
}

public class PriorityExample {
    public static void main(String[] args) {
        PriorityThread low = new PriorityThread("Low");
        PriorityThread high = new PriorityThread("High");

        low.setPriority(Thread.MIN_PRIORITY);
        high.setPriority(Thread.MAX_PRIORITY);

        low.start();
        high.start();
    }
}

Important: Thread priority is a scheduling hint, not a guarantee of execution order. Do not use priorities to enforce correctness or to ensure that one thread always completes before another.

14. Handling Thread Interruption

Interruption is a cooperative mechanism for requesting that a thread stop what it is doing or respond to a cancellation request. It is not the same as forcibly terminating a thread.

class InterruptibleThread extends Thread {
    @Override
    public void run() {
        try {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("Working...");
                Thread.sleep(300);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        System.out.println("Thread is stopping.");
    }
}

public class InterruptExample {
    public static void main(String[] args) throws InterruptedException {
        InterruptibleThread thread = new InterruptibleThread();

        thread.start();
        Thread.sleep(1000);

        thread.interrupt();
        thread.join();

        System.out.println("Main method finished.");
    }
}

Explanation:

  • The worker performs its task until interruption is requested.
  • interrupt() sets the interrupt status and causes certain blocking methods, including sleep(), to throw InterruptedException.
  • The example restores the interrupt status when catching the exception.
  • The worker then finishes, allowing join() to return.

Applications should design interruption handling according to the task's cancellation requirements. Avoid deprecated thread termination methods such as stop().

15. Limitations of Extending the Thread Class

Although extending Thread is useful for learning and small examples, it is not always the best design for larger applications.

  • Single class inheritance: A class that extends Thread cannot extend another class.
  • Task and execution are coupled: The business logic is combined with the thread object.
  • Limited reuse: The task is less flexible when it must be executed by an executor or another execution mechanism.
  • Resource management: Creating many thread objects manually can lead to unnecessary resource usage.

For many real applications, implementing Runnable or Callable and using an ExecutorService provides better separation between the task and the mechanism that executes it.

16. Thread Class vs. Runnable Interface

Feature Extending Thread Implementing Runnable
Approach Subclass the Thread class. Implement the Runnable interface.
Inheritance Uses the class's single inheritance opportunity. Allows the class to extend another class if needed.
Task separation Task logic is placed in the thread subclass. Task logic is separated from the thread object.
Executor compatibility Possible, but the task and thread are more tightly coupled. Works naturally with executor-based APIs.
Best suited for Basic demonstrations and specialized thread subclasses. Reusable tasks and many practical application designs.

17. Common Mistakes When Extending Thread

Mistake Problem Correct Practice
Calling run() directly Executes the method on the current thread rather than starting a new thread. Call start() to initiate a separate thread.
Calling start() twice on the same object Throws IllegalThreadStateException. Create a new thread object for a new execution.
Assuming a fixed output order Thread scheduling is not guaranteed to follow source-code order across threads. Use synchronization or coordination such as join() when order matters.
Ignoring InterruptedException Can cause cancellation requests to be lost. Handle interruption appropriately and restore the interrupt status when necessary.
Sharing mutable data without coordination May cause race conditions or inconsistent results. Use synchronization, locks, atomic variables, or other concurrency utilities.
Creating excessive threads Can increase memory usage and scheduling overhead. Consider thread pools and the Executor framework.

18. Best Practices

  • Override only the methods needed for the intended behavior, usually run() for a basic example.
  • Call start() instead of directly calling run() when a new thread is required.
  • Give threads meaningful names to simplify debugging.
  • Use join() or other coordination mechanisms when task completion must be awaited.
  • Respond to interruption rather than ignoring it.
  • Avoid relying on thread priorities or timing delays to guarantee ordering.
  • Protect shared mutable state against concurrent access.
  • Prefer Runnable and executor-based designs for reusable application tasks unless extending Thread serves a specific purpose.

19. Practice Exercises

  1. Create a class named GreetingThread that extends Thread and prints a greeting message.
  2. Create a thread named CounterThread that prints numbers from 1 to 10.
  3. Use a constructor and super(name) to assign a custom name to a thread.
  4. Create two thread objects that print different messages and observe the output order.
  5. Use Thread.sleep() to add a delay between printed values.
  6. Use join() to make the main thread wait for a worker thread.
  7. Check a thread's state before starting it and after it finishes.
  8. Implement interruption handling so a running thread can stop cooperatively.

20. Mini Project: Multithreaded Number Printer

Objective: Create a simple application that starts two named threads, each printing a sequence of numbers.

Requirements:

  • Create a class named NumberPrinter that extends Thread.
  • Use a constructor to accept a thread name and a maximum number.
  • Override run() to print the numbers from 1 to the maximum.
  • Start two different instances.
  • Use join() so the main thread waits for both threads to finish.
class NumberPrinter extends Thread {
    private final int maxNumber;

    public NumberPrinter(String name, int maxNumber) {
        super(name);
        this.maxNumber = maxNumber;
    }

    @Override
    public void run() {
        for (int i = 1; i <= maxNumber; i++) {
            System.out.println(getName() + " - " + i);
        }
    }
}

public class NumberPrinterProject {
    public static void main(String[] args) {
        NumberPrinter first = new NumberPrinter("Printer-A", 5);
        NumberPrinter second = new NumberPrinter("Printer-B", 5);

        first.start();
        second.start();

        try {
            first.join();
            second.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.out.println("Main thread interrupted.");
            return;
        }

        System.out.println("Both threads completed.");
    }
}

Expected behavior: Both threads print numbers from 1 to 5. Their output may be interleaved, but the final completion message appears only after both threads have terminated.

21. Interview Questions and Answers

Q1. How do you create a thread by extending the Thread class?

Create a subclass of Thread, override its run() method, instantiate the subclass, and call start().

Q2. Why do we override run()?

The run() method defines the task that should execute when the thread starts.

Q3. What happens when start() is called?

The JVM starts a new thread of execution and invokes that thread's run() method.

Q4. Can we start the same thread object twice?

No. Calling start() again on a thread that has already been started throws IllegalThreadStateException, even if the thread has finished.

Q5. What happens when run() is called directly?

It executes like a normal method call on the current thread and does not start a new thread by itself.

Q6. Can a class that extends Thread extend another class as well?

No. Java supports extending only one class. The class can still implement interfaces.

Q7. Why is implementing Runnable often preferred?

It separates task logic from thread management and allows the class to extend another class. It also integrates naturally with executor-based task management.

Q8. What is the purpose of join()?

join() makes the calling thread wait for another thread to terminate.

Q9. What is the purpose of Thread.sleep()?

It pauses the currently executing thread for a specified duration, subject to scheduling and timing precision.

Q10. What is the role of super(name) in a Thread subclass?

It invokes the parent class constructor that assigns a name to the thread.

22. Quick Revision Checklist

  • Thread is available in the java.lang package.
  • Create a subclass using extends Thread.
  • Override public void run() to define the task.
  • Create an instance of the subclass.
  • Call start() to start a new thread of execution.
  • Calling run() directly does not start a new thread.
  • Use super(name) to assign a custom thread name.
  • Use sleep() for timed pauses and join() to wait for completion.
  • Handle interruptions and shared data carefully.
  • Consider Runnable and executors for reusable, production-oriented tasks.

23. Conclusion

Creating threads by extending the Thread class is a fundamental technique for learning Java multithreading. By defining a subclass, overriding run(), and invoking start(), developers can execute tasks on separate threads. Understanding thread naming, lifecycle states, interruption, and coordination methods builds a strong foundation for advanced concurrency concepts.

Although extending Thread is useful for basic examples, separating task logic with Runnable and managing execution through the Executor framework is often more flexible for real-world applications.

Continue learning Core Java: Explore the Core Java Training course, register for a course demo, or visit the JustAcademy website.

Previous Next
whatsapp